﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SimpleData.Data;
using System.Data.Objects;
using SimpleData.ObjectModel;
using SimpleData.Infrastructure.Validation;
using System.Linq.Expressions;
using SimpleData.Infrastructure.Tenancy;

namespace DataAccess.SqlClient
{

	/// <summary>
	/// Objeto que fornece o comportamento básico de um repositório
	/// </summary>
	public abstract class RepositoryBase : IRepository
	{
		private ObjectContext context;

		protected RepositoryBase( IConnectionFactory connectionFactory )
		{
			connectionFactory.Throws().IfNull();

			this.ConnectionFactory = connectionFactory;
		}

		/// <summary>
		/// Obtém o contexto com os objetos que serão materializados
		/// </summary>
		public ObjectContext Context
		{
			get
			{
				if ( this.context == null )
					this.context = this.CreateObjectContext();
					//this.context = this.CreateObjectContext();
				return this.context;
			}
		}

		/// <summary>
		/// Realiza a criação do contexto de objetos e a conexão com o banco de dados
		/// </summary>
		public IConnectionFactory ConnectionFactory
		{
			get;
			private set;
		}

		/// <summary>
		/// Indica se o repositório já foi inicializado
		/// </summary>
		public bool WasInitialized
		{
			get { return this.context != null; }
		}

		/// <summary>
		/// Realiza a criação do contexto de objetos
		/// </summary>
		/// <returns></returns>
		private ObjectContext CreateObjectContext()
		{
			var connection = this.ConnectionFactory.Create();

			// TODO : Adicionar validação e verificar se todas as entidades estão com o tenant corretos

			return connection;
		}

		
		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="TEntity"></typeparam>
		/// <returns></returns>
		private IQueryable<TEntity> Query<TEntity, TFetch>()
			where TEntity : class
			where TFetch : IFetchStrategy<TEntity>, new()
		{
			var query = this.Context.CreateObjectSet<TEntity>();

			// TODO : Adicionar critério por Tenant
			//if ( typeof( IObjectPerTenant ).IsAssignableFrom( typeof( TEntity ) ) )
			//    query = ( ObjectSet<TEntity> )( ( IQueryable<IObjectPerTenant> )query ).Where( app => app.ApplicationID == 1 );

			TFetch fetch = new TFetch();
			return fetch.Configure( query );
		}

		/// <summary>
		/// Realiza uma pesquisa por tenant
		/// </summary>
		/// <typeparam name="TEntity"></typeparam>
		/// <typeparam name="TFetch"></typeparam>
		/// <returns></returns>
		protected IQueryable<TEntity> QueryPerTenant<TEntity, TFetch>()
			where TEntity : class, IObjectPerTenant
			where TFetch : IFetchStrategy<TEntity>, new()
		{
			return this.Query<TEntity, TFetch>().PerTenant();
		}

		#region IRepository Members

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="TEntity"></typeparam>
		/// <returns></returns>
		public virtual TEntity Create<TEntity>()
			where TEntity : class
		{
			return this.Context.CreateObjectSet<TEntity>().CreateObject();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="TEntity"></typeparam>
		/// <param name="entity"></param>
		public virtual void Add<TEntity>( TEntity entity )
			where TEntity : class
		{
			this.Context.CreateObjectSet<TEntity>().AddObject( entity );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="TEntity"></typeparam>
		/// <param name="where"></param>
		/// <returns></returns>
		public virtual IQueryable<TEntity> FindAll<TEntity>( Expression<Func<TEntity, bool>> criteria )
			where TEntity : class
		{
			return this.Query<TEntity, ReadOnlyFetchStrategy<TEntity>>().Where( criteria );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public virtual IEnumerable<EntityValidationResult> SaveChanges()
		{
			List<EntityValidationResult> result = new List<EntityValidationResult>();

			if ( this.WasInitialized )
			{
				var entries = this.Context.ObjectStateManager.GetObjectStateEntries( System.Data.EntityState.Added | System.Data.EntityState.Modified );
				foreach ( var entry in entries )
					if ( !entry.IsRelationship )
						result.Add( EntityValidator.Validate( entry.Entity ) );

				this.Context.SaveChanges();
				this.Context.AcceptAllChanges();
			}

			return result;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// 
		/// </summary>
		public void Dispose()
		{
			if ( this.context != null )
			{
				this.context.Dispose();
				//this.context = null;
			}
		}

		#endregion
	}
}
